package cn.usian.basicInformation.log.logTest;


import cn.hutool.extra.spring.SpringUtil;
import cn.usian.basicInformation.log.mapper.LogMapper;
import cn.usian.pojo.basicInformation.log.BaseOperationLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.StringJoiner;
@Aspect
@Component
@Slf4j
/**
 * 使用Aspect和Component注解定义一个操作日志切面类
 * 该类负责记录系统中的操作日志
 */
public class OperatorLogAspectj {
    /**
     * 定义一个切入点，该切入点关注所有使用了OperLog注解的方法
     */
    @Pointcut("@annotation(cn.usian.basicInformation.log.logTest.OperLog)")
    public void pointcut(){}

    /**
     * 拦截操作日志切点方法
     * 此顾问方法用于围绕着日志切点方法执行，以记录操作日志
     * 主要逻辑包括：获取操作类型、目标对象、ID，比较操作前后的对象变化，并保存日志
     *
     * @param point 切入点对象，用于获取方法执行信息
     * @throws Exception 如果方法执行过程中抛出异常，则进行转发
     */
    @Around("pointcut()")
    public void operatorLog(ProceedingJoinPoint point) throws Exception {
        // 获取操作对象
        Object obj = getArg(point);
        log.info("获取obj:{}" , obj);
        // 获取操作类型
        int type = getType(point);
        log.info("获取type:{}" , type);
        // 获取操作类
        Class clazz = getClazz(point);
        log.info("获取clazz:{}" , clazz);
        // 获取转换类
        Class convertClass = getConvertClass(point);
        log.info("获取convertClass:{}" , convertClass);
        // 获取对象ID
        Long originId = getId(obj);
        log.info("获取originId:{}" , originId);

        // 获取原始对象
        Object originObj = getObj(clazz,convertClass,originId);
        log.info("获取originObj:{}" , originObj);

        try {
            // 继续执行目标方法
            point.proceed();
        } catch (Throwable e) {
            // 将接收到的异常转发出去
            throw new RuntimeException(e);
        }

        // 获取更新后的对象
        Object updateObj = getObj(clazz,convertClass,originId);
        log.info("获取updateObj:{}" , updateObj);

        // 构建日志对象
        BaseOperationLog operatorLog = createOperLog(originObj,updateObj,type,originId);
        log.info("获取operatorLog:{}" , operatorLog);

        // 如果日志对象为空，则直接返回
        if(Objects.isNull(operatorLog)) {
            return;
        }
        // 保存日志

        saveLog(operatorLog);
    }

    /**
     * 获取ProceedingJoinPoint对象中的类类型
     * @param point 切入点
     * @return 类类型
     */
    private Class getClazz(ProceedingJoinPoint point) {
        OperLog log = getLog(point);
        return log.clazz();
    }

    /**
     * 获取ProceedingJoinPoint对象中转换的类类型
     * @param point 切入点
     * @return 转换的类类型
     */
    private Class getConvertClass(ProceedingJoinPoint point) {
        OperLog log = getLog(point);
        return log.convertClass();
    }

    /**
     * 获取ProceedingJoinPoint对象中的日志类型
     * @param point 切入点
     * @return 日志类型
     */
    private int getType(ProceedingJoinPoint point) {
        OperLog log = getLog(point);
        return log.type();
    }

    /**
     * 从ProceedingJoinPoint对象中获取OperLog注解
     * @param point 切入点
     * @return OperLog注解
     * @throws RuntimeException 如果接口缺少OperLog注解则抛出异常
     */
    private OperLog getLog(ProceedingJoinPoint point) {
        OperLog log = getAnnotationLog(point);
        if(Objects.isNull(log)) {
            throw new RuntimeException("接口缺少OperLog注解");
        }
        return log;
    }

    /**
     * 保存日志对象
     * @param operatorLog 日志对象
     */
    private void saveLog(BaseOperationLog operatorLog) {
        LogMapper mapper = SpringUtil.getBean(LogMapper.class);
        mapper.save(operatorLog);
    }

    /**
     * 创建操作日志对象
     * @param originObj 原始对象
     * @param updateObj 更新后的对象
     * @param type 日志类型
     * @param originId 原始ID
     * @return 操作日志对象
     * @throws IllegalAccessException 可能抛出的异常
     */
    private BaseOperationLog createOperLog(Object originObj, Object updateObj, int type, Long originId) throws IllegalAccessException {
        StringJoiner sj = new StringJoiner("|");
        Class<?> clazz = originObj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Object originField = getField(field,originObj);
            Object updateField = getField(field,updateObj);

            //如果属性变更，则记录
            if(!originField.equals(updateField)) {
                CoverItem coverItem = field.getAnnotation(CoverItem.class);
                isValidCoverItem(coverItem,field);
                if(!coverItem.isCheck()) {
                    continue;
                }
                String desc = createLogDesc(field,originField,updateField);
                sj.add(desc);
            }
        }
        if(StringUtils.isBlank(sj.toString())) {
            return null;
        }
        BaseOperationLog operatorLog = buildOperLog(sj);
        operatorLog.setOperationType(type);
        operatorLog.setUpdateId(originId);

        return operatorLog;
    }

    /**
     * 获取指定对象的指定字段的值
     * @param field 字段
     * @param originObj 原始对象
     * @return 字段值
     * @throws IllegalAccessException 可能抛出的异常
     */
    private Object getField(Field field,Object originObj) throws IllegalAccessException {
        Object obj = field.get(originObj);
        if(Objects.isNull(obj)) {
            obj = "null";
        }
        return obj;
    }

    /**
     * 构建操作日志对象
     * @param sj 字段变更描述
     * @return 操作日志对象
     */
    private BaseOperationLog buildOperLog(StringJoiner sj) {
        String desc = sj.toString();
        BaseOperationLog operator = new BaseOperationLog();
        return operator;
    }

    /**
     * 创建字段变更描述
     * @param field 字段
     * @param originField 原始字段值
     * @param updateField 更新后的字段值
     * @return 字段变更描述
     */
    private String createLogDesc(Field field, Object originField, Object updateField) {
        CoverItem annotation = field.getAnnotation(CoverItem.class);
        String desc = "将字段[%s]，从[%s]变更为[%s]";
        desc = String.format(desc,annotation.value(),originField,updateField);
        return desc;
    }

    /**
     * 验证字段上的CoverItem注解是否有效
     * @param coverItem CoverItem注解
     * @param field 字段
     * @throws RuntimeException 如果字段缺少CoverItem注解则抛出异常
     */
    private void isValidCoverItem(CoverItem coverItem, Field field) {
        if(Objects.isNull(coverItem)) {
            throw new RuntimeException(field.getName()+"缺少@CoverItem注解");
        }
    }

    /**
     * 根据类类型和ID获取对象
     * @param clazz 类类型
     * @param convertClass 转换的类类型
     * @param id ID
     * @return 对象
     * @throws Exception 可能抛出的异常
     */
    private Object getObj(Class clazz, Class convertClass, Long id) throws Exception {
        Object obj = SpringUtil.getBean(clazz);
        log.info("获取对象："+obj);
        Method method = clazz.getMethod("selectById", Serializable.class);
        log.info("获取方法："+method);
        Object invoke = method.invoke(obj, id);
        log.info("获取对象："+invoke);
        Object convert = CustomerConvert.convert(invoke, convertClass);
        log.info("转换对象："+convert);
        return convert;
    }

    /**
     * 获取对象的ID字段值
     * @param obj 对象
     * @return ID字段值
     * @throws Exception 可能抛出的异常
     */
    private Long getId(Object obj) throws Exception {
        Field field = obj.getClass().getDeclaredField("id");
        field.setAccessible(true);
        Long id = (Long) field.get(obj);
        return id;
    }

    /**
     * 获取ProceedingJoinPoint对象中的第一个参数
     * @param point 切入点
     * @return 第一个参数
     * @throws RuntimeException 如果参数为空或长度不足则抛出异常
     */
    private Object getArg(ProceedingJoinPoint point) {
        Object[] args = point.getArgs();
        if(Objects.isNull(args) || args.length < 1) {
            throw new RuntimeException("参数异常");
        }
        return args[0];
    }

    /**
     * 从ProceedingJoinPoint对象中获取OperLog注解
     * @param point 切入点
     * @return OperLog注解
     */
    private OperLog getAnnotationLog(ProceedingJoinPoint point) {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if(Objects.nonNull(method)) {
            return method.getAnnotation(OperLog.class);
        }
        return null;
    }


}


